/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.hannesdorfmann.mosby3;

import com.hannesdorfmann.mosby3.mvi.MviPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.util.UUID;

/**
 * The default implementation of {@link AbilitySliceMviDelegate}
 * <p>
 * The View is attached to the Presenter in {@link AbilitySlice#onStart(Intent)}.
 * So you better instantiate all your UI widgets before that lifecycle callback (typically in
 * {@link * Fragment#onStart()}. The View is detached from Presenter in
 * {@link AbilitySlice#onStop()}. {@link MviPresenter#destroy()} is called from {@link
 * AbilitySlice#onStop()} if Fragment will be destroyed permanently.
 * </p>
 *
 * @param <V> The type of {@link MvpView}
 * @param <P> The type of {@link MviPresenter}
 * @see AbilitySliceMviDelegate
 */
public class AbilitySliceMviDelegateImpl<V extends MvpView, P extends MviPresenter<V, ?>>
    implements AbilitySliceMviDelegate<V, P> {

  public final static boolean DEBUG = false;
  private static final String TAG = "FragmentMviDelegateImpl";
  private static final HiLogLabel DEBUG_TAG = new HiLogLabel(HiLog.LOG_APP, 0, TAG);
  private static final String KEY_MOSBY_VIEW_ID = "com.hannesdorfmann.mosby3.fragment.mvi.id";

  private String mosbyViewId = null;
  private MviDelegateCallback<V, P> delegateCallback;
  private AbilitySlice fragment;
  private boolean onViewCreatedCalled = false;
  private final boolean keepPresenterDuringScreenOrientationChange;
  private final boolean keepPresenterOnBackstack;
  private P presenter;
  private boolean viewStateWillBeRestored;
  private static final String BASE_URL = "https://github.com/sockeqwe/mosby";
  private static final String ISSURE = "issues";

  public AbilitySliceMviDelegateImpl( MviDelegateCallback<V, P> delegateCallback,
       AbilitySlice fragment) {
    this(delegateCallback, fragment, true, true);
  }

  public AbilitySliceMviDelegateImpl( MviDelegateCallback<V, P> delegateCallback,
       AbilitySlice fragment, boolean keepPresenterDuringScreenOrientationChange,
      boolean keepPresenterOnBackstack) {
    if (delegateCallback == null) {
      throw new NullPointerException("MviDelegateCallback is null!");
    }

    if (fragment == null) {
      throw new NullPointerException("Fragment is null!");
    }

    if (!keepPresenterDuringScreenOrientationChange && keepPresenterOnBackstack) {
      throw new IllegalArgumentException("It is not possible to keep the presenter on backstack, "
          + "but NOT keep presenter through screen orientation changes. Keep presenter on backstack also "
          + "requires keep presenter through screen orientation changes to be enabled");
    }

    this.delegateCallback = delegateCallback;
    this.fragment = fragment;
    this.keepPresenterDuringScreenOrientationChange = keepPresenterDuringScreenOrientationChange;
    this.keepPresenterOnBackstack = keepPresenterOnBackstack;
  }


  private boolean retainPresenterInstance(boolean keepPresenterOnBackstack, Ability activity,
      AbilitySlice fragment) {

    if (activity.isUpdatingConfigurations()) {
      if (keepPresenterDuringScreenOrientationChange) {
        return true;
      }
      return false;
    }

    if (activity.isTerminating()) {
      return false;
    }

    if (keepPresenterOnBackstack) {
      return true;
    }

    return false;
  }



   private Ability getActivity() {
    Ability activity = fragment.getAbility();
    if (activity == null) {
      throw new NullPointerException(
          "Activity returned by Fragment.getActivity() is null. Fragment is " + fragment);
    }

    return activity;
  }

  /**
   * Generates the unique (mosby internal) viewState id and calls {@link
   * MviDelegateCallback#createPresenter()}
   * to create a new presenter instance
   *
   * @return The new created presenter instance
   */
  private P createViewIdAndCreatePresenter() {

    P presenter = delegateCallback.createPresenter();
    if (presenter == null) {
      throw new NullPointerException(
          "Presenter returned from createPresenter() is null. Fragment is " + fragment);
    }
    if (keepPresenterDuringScreenOrientationChange || keepPresenterOnBackstack) {
      Ability activity = getActivity();
      mosbyViewId = UUID.randomUUID().toString();
      PresenterManager.putPresenter(activity, mosbyViewId, presenter);
    }
    return presenter;
  }


  @Override
  public void onStart(Intent intent) {

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG,
              "MosbyView ID = " + mosbyViewId + " for MvpView: " + delegateCallback.getMvpView());
    }

    if (mosbyViewId == null) {
      // No presenter available,
      // Activity is starting for the first time (or keepPresenterDuringScreenOrientationChange == false)
      presenter = createViewIdAndCreatePresenter();
      viewStateWillBeRestored = false;
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG, "new Presenter instance created: " + presenter);
      }
    } else {
      presenter = PresenterManager.getPresenter(getActivity(), mosbyViewId);
      if (presenter == null) {
        // Process death,
        // hence no presenter with the given viewState id stored, although we have a viewState id
        presenter = createViewIdAndCreatePresenter();
        viewStateWillBeRestored = false;
        if (DEBUG) {
          HiLog.debug(DEBUG_TAG,
                  "No Presenter instance found in cache, although MosbyView ID present. This was caused by process death, therefore new Presenter instance created: "
                          + presenter);
        }
      } else {
        viewStateWillBeRestored = true;
        if (DEBUG) {
          HiLog.debug(DEBUG_TAG, "Presenter instance reused from internal cache: " + presenter);
        }
      }
    }

    if (presenter == null) {
      StringBuilder sb = new StringBuilder();
      sb.append(BASE_URL);
      sb.append(ISSURE);
      throw new IllegalStateException(
              "Oops, Presenter is null. This seems to be a Mosby internal bug. Please report this issue here: "+sb.toString());
    }
  }


  @Override
  public void onStop() {
    Ability activity = getActivity();
    boolean retainPresenterInstance =
            retainPresenterInstance(keepPresenterOnBackstack, activity, fragment);

    if (!retainPresenterInstance) {
      presenter.destroy();
      if (mosbyViewId
              != null) { // mosbyViewId == null if keepPresenterDuringScreenOrientationChange == false
        PresenterManager.remove(activity, mosbyViewId);
      }
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG, "Presenter destroyed");
      }
    } else if (DEBUG) {
      HiLog.debug(DEBUG_TAG, "Retaining presenter instance: "
              + Boolean.toString(retainPresenterInstance)
              + " "
              + presenter);
    }

    presenter = null;
    delegateCallback = null;
    fragment = null;
  }

  @Override
  public void onActive() {
// presenter is ready, so attach viewState
    V view = delegateCallback.getMvpView();
    if (view == null) {
      throw new NullPointerException(
              "MvpView returned from getMvpView() is null. Returned by " + fragment);
    }

    if (presenter == null) {
      StringBuilder sb = new StringBuilder();
      sb.append(BASE_URL);
      sb.append(ISSURE);
      throw new IllegalStateException(
              "Oops, Presenter is null. This seems to be a Mosby internal bug. Please report this issue here: "+sb.toString());
    }

    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(true);
    }

    presenter.attachView(view);

    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(false);
    }

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG,
              "MvpView attached to Presenter. MvpView: " + view + "   Presenter: " + presenter);
    }
  }


  @Override
  public void onInactive() {

  }

  @Override
  public void onForeground(Intent intent) {

  }

  @Override
  public void onBackground() {
    presenter.detachView();
    viewStateWillBeRestored = true; // used after screen orientation if retaining Fragment

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG, "detached MvpView from Presenter. MvpView "
              + delegateCallback.getMvpView()
              + "   Presenter: "
              + presenter);
    }
  }

}
